import java.util.*;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.event.*;
import java.io.File;
import java.lang.Math;

import javax.swing.*;

import jpen.event.PenAdapter;
import jpen.PButton;
import jpen.PButtonEvent;
import jpen.PenManager;
import jpen.PKind;
import jpen.PLevel;
import jpen.PLevelEvent;

/**
 * Simple sketch panel - to draw / record user sketch 
 * - only x , y , time data recorded
 * - Pressure , Tilt support can be added 
 * @author manoj
 *
 */
public class DrawPanel extends JPanel implements MouseListener, MouseMotionListener
{
    // some constants for drawing settings
    private static final int C_THICKNESS = 1;
    private static final int C_DARKNESS = 2;
    private static final int C_COLOR = 3;
    private static final int C_COLOR2 = 4;

    private Persona currentPersona; // source persona
    private int activePersona; // number for transform persona

	public static final int lineSize = 30;

	private static final long serialVersionUID = 1L;	
	
	double totalTime;
	
	Sketch s; // sketch object to record the strokes drawn
	
	final PenManager penManager;

    // DrawingSettings
    private int pressureAction = 0;
    private int tiltAction = 0;
	private int otherAction = 0;
	
	DrawPanel()
	{		
		s = new Sketch();	
		
		addMouseListener(this);
		addMouseMotionListener(this);		
		
		this.penManager=new PenManager(this);
		penManager.pen.setFirePenTockOnSwing(true);
		penManager.pen.setFrequencyLater(40);
		penManager.pen.levelEmulator.setPressureTriggerForLeftCursorButton(0.5f);
	}	

	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) 
	{
		
		//try{d.persist();}catch(Exception m){;}
		repaint();
	}
	public void mouseMoved(MouseEvent e) {}
	
	public void mouseClicked(MouseEvent e) 
	{	
		
	}
	
	public void mousePressed(MouseEvent e)
	{		
		// Mouse down is equivalent to the Pen down event
		// - creating a new Stroke object for the pen down
		ArrayList<Point> dataPoints = new ArrayList<Point>();
		Stroke newStroke = new Stroke(dataPoints);
		s.strokeList.add(newStroke);			
	}

	
	public void mouseReleased(MouseEvent e) 
	{
            Stroke stroke = new Stroke();
            stroke = s.strokeList.get(s.strokeList.size() - 1);
            s.strokeList.remove(s.strokeList.size() - 1);
            
            /*stroke.dataPoints = personify(stroke);
            s.strokeList.add(stroke);*/
            
            ArrayList<Stroke> newstrokes = personify(stroke);
            s.strokeList.addAll(newstrokes);

            this.repaint();
            System.out.println("number of new strokes: " + newstrokes.size());
            System.out.println("number of strokes: " + s.strokeList.size());
            /*System.out.println(stroke.dataPoints.get(1).tilt_x);
            System.out.println(stroke.dataPoints.get(1).tilt_y);
            (Stroke st = s.strokeList.get(s.strokeList.size()-1);;
            Features.printStrokeValueHeaders();
            Features.printStrokeValues(st);
            System.out.println("");*/
	}

	/**
	 * Collecting all the points between pen down and pen up - which is Mouse drag event
	 */
	public void mouseDragged(MouseEvent e) 
	{		
		// Recording the current mouse point and time
		int x=e.getX();
		int y=e.getY();
	    float pressure=penManager.pen.getLevelValue(PLevel.Type.PRESSURE);
	    float tilt_x = Math.abs(penManager.pen.getLevelValue(PLevel.Type.TILT_X));
        float tilt_y = Math.abs(penManager.pen.getLevelValue(PLevel.Type.TILT_Y));
		double t = System.currentTimeMillis();		
		
		// Creating a point
		Point pnt = new Point(x,y,t,pressure,tilt_x,tilt_y);
		
		// Adding the point to the current stroke
		Stroke currStroke = s.strokeList.get(s.strokeList.size()-1);		
		currStroke.dataPoints.add(pnt);
		this.repaint();		
	}
	
	/**
	 * Function to draw the strokes on the panel
	 * @param g2d
	 */
	public void drawRawDiagram(Graphics2D g2d)
	{
		
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		
		
		ArrayList<Stroke> sl = s.strokeList;
		
		// Setting the color of the sketch
		g2d.setColor(Color.BLACK);		
		
		try
		{
			// Rendering each stroke on the panel
			for(int j=0;j<sl.size();j++)
			{
				
				Stroke s = sl.get(j);
				ArrayList <Point> dataPoints = s.dataPoints;
				for(int k=0;k<dataPoints.size()-1;k++)
				{
					Point p = dataPoints.get(k);
					Point p1 = dataPoints.get(k+1);
					
					int x = (int) p.x_coor;
					int y = (int) p.y_coor;
					int x1 = (int) p1.x_coor;
					int y1 = (int) p1.y_coor;
					
					Line2D.Double line = new Line2D.Double(x,y,x1,y1);
										
					if(tiltAction == C_THICKNESS){
                                            /*double tx = Math.cos(p1.tilt_x);
                                            double ty = Math.cos(p1.tilt_y);
                                            System.out.println(p1.tilt_x);*/
					    //System.out.println(.5*Math.sqrt((tx*tx)+(ty*ty)));.5*Math.sqrt((tx*tx)+(ty*ty)
					    g2d.setStroke(new BasicStroke((float)((1-((p1.tilt_x+p1.tilt_y)/2.)) * lineSize), java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND));
					}
					else{
					    // set half of the max default thickness if tilt doesn't control thickness. If pressure controls it then this will be overwritten.
					    g2d.setStroke(new BasicStroke((float)(lineSize/2.0), java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND)); 
					}
					
					if(pressureAction == C_THICKNESS){
					    g2d.setStroke(new BasicStroke((p1.pressure * lineSize), java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND));
					}
					if(pressureAction == C_DARKNESS){
					    g2d.setColor(new Color((float) Math.abs(1.0 - p1.pressure), (float) Math.abs(1.0 - p1.pressure), (float) Math.abs(1.0 - p1.pressure)));
					}
					else{
					    
					}
					
					if(otherAction == C_COLOR){
					    g2d.setColor(new Color(p1.tilt_y, p1.tilt_x, Math.abs(p1.pressure-Math.abs(p1.tilt_x-p1.tilt_y))));
					}
					else if(otherAction == C_COLOR2){
					    g2d.setColor(new Color(y%255, x%255, Math.abs(x%255-y%255)));
					}
					else{
					
					}
					
					g2d.draw(line);
				}
				
			}
		}catch(NullPointerException e){}
		
	}
	
	
	public void paint(Graphics g1)
	{
		super.paint(g1);
		Graphics2D g2d = (Graphics2D) g1;
		g2d.setColor(new Color(0,0,0));
		
		//Redrawing the strokes on the panel.
		drawRawDiagram(g2d);
	}

    private static void printSettingForControl(int c, String s){
        if(c==0){
            System.out.println("" + s + " controls nothing");
        }
        else if(c==C_THICKNESS){
            System.out.println("" + s + " controls Thickness");
        }
        else if(c==C_DARKNESS){
            System.out.println("" + s + " controls Weight");
        }
        else if(c==C_COLOR){
            System.out.println("" + s + ", Magic Color is activated");
        }
        else if(c==C_COLOR2){
            System.out.println("" + s + ", Earth Color is activated");
        }
    }
    
	private void printSettings(){
	    System.out.println("::Settings::");
        printSettingForControl(this.pressureAction, "Pressure");
        printSettingForControl(this.tiltAction, "Tilt");
        printSettingForControl(this.otherAction, "Also");
	}
	
	private ArrayList<Stroke> personify(Stroke stroke){
            ArrayList<Stroke> newStrokes = new ArrayList<Stroke>();
            Stroke s1 = stroke;
	    double aVal, bVal;
	    switch(activePersona){
	        case 1:
	            aVal = Features.secondQuarterVersusLastQuarterPressure(s1);
	            bVal = currentPersona.getFeatureAverage(Persona.SIZE_BOUNDING_BOX);
	            s1.dataPoints = Modifiers.unifyPressures(s1, 0.3*bVal, 0.5);
	            s1.dataPoints = Modifiers.increaseJitter(s1, 20.0-2*aVal, 2);
	            s1.dataPoints = Modifiers.translateByCenter(s1, 0.5*aVal);
	            newStrokes.add(s1);
                    break;
	        case 2:
	            s1.dataPoints = Modifiers.increaseJitter(s1, 20.0, 2);
	            s1.dataPoints = Modifiers.divergePressures(s1, 0.15, 0.5);
	            newStrokes.add(s1);
                    break;
	        case 3:
                    double tightenFactor = currentPersona.getFeatureAverage(Persona.TIME_STROKE);
                    ArrayList<Integer> changes = Features.curvatureChanges(s1);
                    s1.dataPoints = Modifiers.tightenCurvature(s1, changes, tightenFactor);
                    double jitterFactor = currentPersona.getFeatureAverage(Persona.MIDDLE_TO_ENDS_PRESSURE_DIFFERENCE);
                    s1.dataPoints = Modifiers.increaseJitter(s1, jitterFactor * 20, 1);
	            newStrokes.add(s1);
                    Stroke s2 = new Stroke(stroke, 0, stroke.dataPoints.size() - 1);
                    System.out.println(currentPersona.getFeatureAverage(Persona.TOTAL_SPEED));
                    double scaleXFactor = currentPersona.getFeatureAverage(Persona.TOTAL_SPEED);
                    s2.dataPoints = Modifiers.scaleStrokeX(s2, scaleXFactor * 0.9);
                    jitterFactor = currentPersona.getFeatureAverage(Persona.AVERAGE_PRESSURE);
                    s2.dataPoints = Modifiers.increaseJitter(s2, jitterFactor * 30, 1);
                    s2.dataPoints = Modifiers.unifyPressures(s2, 0.05, 0.9);
                    s2.dataPoints = Modifiers.divergeTilt(s2, 0.8, 0.5);
                    s2.dataPoints = Modifiers.divergeTilt(s2, totalTime, 0.8);
                    newStrokes.add(s2);
                    break;
	        case 4:
	            aVal = Features.secondQuarterVersusLastQuarterPressure(stroke);
	            s1.dataPoints = Modifiers.invertPressures(s1);
	            s1.dataPoints = Modifiers.unifyPressures(s1, 0.3+aVal, 0.5);
	            s1.dataPoints = Modifiers.increaseJitter(s1, 20.0-2*aVal, (int)(2*0.7/currentPersona.getFeatureAverage(Persona.STROKE_JERKINESS)));
	            s1.dataPoints = Modifiers.scaleStrokeY(s1, Features.weight(stroke)%1);
	            s1.dataPoints = Modifiers.smoothCurvature(s1, currentPersona.getFeatureAverage(Persona.TOTAL_CURVATURE)*10);
	            newStrokes.add(s1);
                    break;
	        case 5:
                    double c5pressure = currentPersona.getFeatureAverage(Persona.AVERAGE_PRESSURE);
                    double c5averageTilt = currentPersona.getFeatureAverage(Persona.AVERAGE_TILT);
                    System.out.println(currentPersona.getFeatureAverage(Persona.AVERAGE_TILT));
                    s1.dataPoints = Modifiers.smoothCurvature(stroke, c5averageTilt);
                    s1.dataPoints = Modifiers.unifyPressures(stroke, c5pressure, .3);
                    newStrokes.add(s1);
                    break;
	        default:
	            newStrokes.add(s1);
	            break;
	    }
	    return newStrokes;
	}
	
	public boolean pressureThickness(){
	    this.pressureAction = C_THICKNESS;
	    this.printSettings();
	    return true;
	}
	
	public boolean pressureWeights(){
	    this.pressureAction = C_DARKNESS;
	    this.printSettings();
	    return true;
	}
	
	public boolean magicColors(){
	    this.otherAction = C_COLOR;
	    this.printSettings();
	    return true;
	}
	public boolean earthColors(){
	    this.otherAction = C_COLOR2;
	    this.printSettings();
	    return true;
	}
	
	public boolean tiltThickness(){
	    this.tiltAction = C_THICKNESS;
	    this.printSettings();
	    return true;
	}
	
	public boolean tiltClear(){
	    this.tiltAction = 0;
	    this.printSettings();
	    return true;
	}
	
	public boolean pressureClear(){
	    this.pressureAction = 0;
	    this.printSettings();
	    return true;
	}
	
    public boolean otherClear(){
	    this.otherAction = 0;
	    this.printSettings();
	    return true;
	}
	
	public boolean clearCanvas(){
	    s.strokeList = null;
	    repaint();
	    s.strokeList = new ArrayList<Stroke>();
	    return true;
	}
	
	public boolean createPersona(){
	    this.currentPersona = new Persona(s.strokeList);
	    return true;
	}
	
	public boolean activatePersona(int p){
	    if(p>5 || p<0) return false;
	    this.activePersona = p;
	    return true;
	}
}
